"""Copyright (c)2006 by Nicholas Borko.  All Rights Reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Read LICENSE.TXT for licensing information.
"""

from mod_python import apache
import os, sys, traceback

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

class WSGIInput(object):
    """Emulate a file-like object for wsgi.input."""
    def __init__(self, req):
        self._req = req

    def read(self, size = None):
        if size is None:
            return self._req.read()
        else:
            return self._req.read(size)

    def readline(self):
        # NOTE: WSGI forbids the optional size argument, even though
        # mod_python supports it
        return self._req.readline()

    def readlines(self, hint = None):
        if hint is None:
            return self._readlines()
        else:
            return self._readlines(hint)

    def __iter__(self):
        while self._req.read_length < self._req.clength:
            yield self._req.readline()


class WSGIError(object):
    """Emulate a file-like object for wsgi.error."""
    def __init__(self, req):
        self._req = req

    def flush(self):
        # since we're not buffering output, this is a no-op
        pass

    def write(self, output):
        # split according to \n, because mod_python logs as r'\n'
        for line in output.split('\n'):
            self.req.log_error(line)

    def writelines(self, seq):
        self.write(''.join(seq))


class handler(object):
    """This is a sample WSGI implementation for mod_python.
    Sample apache configuration usage:

    PythonHandler wsgi_handler
    PythonOption WSGI.Application module::object

    Where module can be a full module name (package dot notation is accepted),
    and the object is the name of an object inside the module.

    Object can also contain dots, in which case it will be resolved from left
    to right. During resolution, if an object of type <class> is encountered,
    it will try instantiating it with no arguments."""

    def __init__(self, req):
        self.req = req

        # get relevant information from the apache config
        config = self.req.get_config()
        debug = int(config.get('PythonDebug', 0))
        auto_reload = int(config.get('PythonAutoReload', 0))
        options = self.req.get_options()

        # get the WSGI application specified by PythonOption WSGI.Application
        self.application = options.get('WSGI.Application', False)

        # bail out if not specified
        if not self.application:
            self.req.log_error('PythonOption WSGI.Application not specified, aborting')
            raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR

        # break the application string into module and handler function
        try:
            app_module, app_handler = self.application.split('::', 1)
        except:
            self.req.log_error('PythonOption WSGI.Application "%s" incorrect format; must be module::handler')
            raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR

        # import the module
        module = apache.import_module(app_module, autoreload = auto_reload,
                                      log = debug)

        # instantiate the object or get the function
        app_object = apache.resolve_object(module, app_handler, silent = True)

        if not app_object:
            self.req.log_error('WGI.Application "%s" not found, aborting')
            raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR

        # add common CGI variables
        self.req.add_common_vars()
        environ = dict(self.req.subprocess_env.items())

        # just in case not *all* headers get converted (e.g. content-length)
        for header, content in self.req.headers_in.items():
            environ['HTTP_%s' % header.upper().replace('-', '_')] = content

        # required for WSGI to work properly
        environ['PATH_TRANSLATED'] = self.req.filename
        # this should be right...
        environ['PATH_INFO'] = environ['SCRIPT_NAME']

        environ['wsgi.input'] = WSGIInput(self.req)
        environ['wsgi.errors'] = WSGIError(self.req)
        environ['wsgi.version'] = (1,0)
        environ['wsgi.multithread'] = apache.mpm_query(apache.AP_MPMQ_IS_THREADED)
        environ['wsgi.multiprocess'] = (apache.mpm_query(apache.AP_MPMQ_MAX_THREADS) > 1) or (apache.mpm_query(apache.AP_MPMQ_MAX_DAEMONS) > 1)
        environ['wsgi.run_once'] = False

        if environ.get('HTTPS','off') in ('on','1'):
            environ['wsgi.url_scheme'] = 'https'
        else:
            environ['wsgi.url_scheme'] = 'http'

        # tracking variables
        self.response_started = False
        self.status = '200 OK'
        self.response_headers = []
        self.headers_sent = False

        result = app_object(environ, self.start_response)

        try:
            try:
                for data in result:
                    if data:
                        self.write(data)
                if not self.headers_sent:
                    self.write('')   # send headers if body was empty
            finally:
                if hasattr(result,'close'):
                    result.close()
        except:
            tb = StringIO()
            traceback.print_exc(None, tb)
            self.req.log_error('WSGI.Application "%s": Uncaught exception, traceback follows:')
            self.req.log_error(tb.getvalue())
            raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR

        raise apache.SERVER_RETURN, apache.OK

    def write(self, data):
        if not self.response_started:
            self.req.log_error('WSGI.Application "%s": write() before start_response()' % self.application)
            raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR

        elif not self.headers_sent:
             self.req.status = int(self.status[:3])
             for header in self.response_headers:
                 if header[0].lower() == 'content-type':
                     self.req.content_type = header[1]
                 else:
                     self.req.headers_out.add(*header)
             self.headers_sent = True

        self.req.write(data)

    def start_response(self, status, response_headers, exc_info = None):
        if exc_info is not None:
            if self.headers_sent:
                tb = StringIO()
                traceback.print_exception(exc_info[0], exc_info[1],
                                          exc_info[2], None, tb)
                exc_info = None
                self.req.log_error('WSGI.Application "%s": Application returned exception, traceback follows:')
                self.req.log_error(tb.getvalue())
                raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
        elif self.headers_sent:
            self.req.log_error('WSGI.Application "%s": Headers already sent!' % self.application)
            raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR

        self.status = status
        self.response_headers[:] = response_headers
        self.response_started = True

        return self.write
